﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FeedbackNetwork.network
{
    public class FloatTensor: Tensor<float>
    {
        public FloatTensor(float[] data, int[] shape): base(data, shape, TensorType.TENSOR_FLOAT)
        {
            
        }

        public FloatTensor()
        {

        }

        public FloatTensor(int[] shape, float init_data)
        {
            this.shape = shape;
            int data_length = this.GetLength();
            float[] data = new float[data_length];
            for (int i = 0; i < data_length; i++) data[i] = init_data;
            this.SetData(data, shape, TensorType.TENSOR_FLOAT);
        }

        public FloatTensor(FloatTensor target):base(target)
        {

        }

        public void SetData(float[] data, int[] shape)
        {
            this.SetData(data, shape, TensorType.TENSOR_FLOAT);
        }

        public void SetData(float data, int[] pos)
        {
            this.SetData(data, pos, TensorType.TENSOR_FLOAT);
        }

        public FloatTensor Mul(float data)
        {
            float[] result = new float[this.data.Length];
            for(int i = 0; i < result.Length; i++)
            {
                result[i] = this.data[i] * data;
            }
            return new FloatTensor(result, this.shape);
        }


        /// <summary>
        /// 对应位置相乘的矩阵乘法(MxN与MxN)
        /// </summary>
        /// <param name="target">MxN, this=MxN</param>
        /// <returns>MxN</returns>
        public FloatTensor Mul(FloatTensor target)
        {
            if (!this.CompareShape(target.GetShape())) throw new ArgumentException("the shape of target is not same with this!");

            float[] result = new float[target.GetLength()];
            float[] target_data = target.GetData();
            for(int i = 0; i < result.Length; i++)
            {
                result[i] = target_data[i] * this.data[i];
            }
            return new FloatTensor(result, target.GetShape());
        }

        public FloatTensor Add(float s)
        {
            FloatTensor result = new FloatTensor(this);
            float[] result_data = result.GetData();
            for (int i = 0; i < result_data.Length; i++)
                result_data[i] += s;
            result.SetData(result_data, result.GetShape());
            return result;
        }

        public FloatTensor Add(FloatTensor target)
        {
            FloatTensor result = new FloatTensor(target);
            float[] result_data = result.GetData();
            for (int i = 0; i < result_data.Length; i++)
                result_data[i] += this.data[i];
            result.SetData(result_data, result.GetShape());
            return result;
        }

        /// <summary>
        /// 行相乘 MxN, 1xN
        /// </summary>
        /// <param name="target">1xN</param>
        /// <returns>MxN</returns>
        public FloatTensor RowMul(FloatTensor target)
        {
            if (target.GetDimensionShape(0) != 1) throw new ArgumentException("target dimension is not match!");

            int[] result_shape = new int[] { this.shape[0], this.shape[1] };
            float[] result_data = new float[this.shape[0]*this.shape[1]];
            float[] target_data = target.GetData();

            for(int i = 0; i < this.shape[0]; i++)
            {
                for(int j = 0; j < this.shape[1]; j++)
                {
                    result_data[i * this.shape[1] + j] = this.data[i * this.shape[1] + j] * target_data[j];
                }
            }

            return new FloatTensor(result_data, result_shape);
        }

 
        /// <summary>
        /// 列相乘 MxN与Mx1相乘
        /// </summary>
        /// <param name="target">Mx1</param>
        /// <returns>MxN</returns>
        public FloatTensor ColMul(FloatTensor target)
        {
            if (target.GetDimensionShape(1) != 1) throw new ArgumentException("target dimension is not match!");

            int[] result_shape = new int[] { this.shape[0], this.shape[1] };
            float[] result_data = new float[this.shape[0] * this.shape[1]];
            float[] target_data = target.GetData();

            for (int i = 0; i < this.shape[0]; i++)
            {
                for (int j = 0; j < this.shape[1]; j++)
                {
                    result_data[i * this.shape[1] + j] = this.data[i * this.shape[1] + j] * target_data[i];
                }
            }

            return new FloatTensor(result_data, result_shape);
        }

        public void ReplaceElement(float source, float target)
        {
            for(int i = 0; i < this.data.Length; i++)
            {
                if (this.data[i] == source) this.data[i] = target;
            }
        }
        private static  float RowColMul(FloatTensor row, FloatTensor col)
        {
            float result = 0f;
            int[] row_shape = row.GetShape();
            int[] col_shape = col.GetShape();
            if (row_shape[0] != 1 || col_shape[1] != 1 || col_shape[0] != row_shape[1]) return result;

            float[] row_data = row.GetData();
            float[] col_data = col.GetData();
            for (int i = 0; i < row_shape[1]; i++)
                result += row_data[i] * col_data[i];

            return result;
        }

        public float Sum()
        {
            float sum = 0f;
            for (int i = 0; i < this.data.Length; i++) sum += this.data[i];
            return sum;
        }

        public FloatTensor Sum_Tensor()
        {
            return new FloatTensor(new float[] { this.Sum() }, new int[] { 1 });
        }

        /// <summary>
        /// 矩阵乘法，行乘以列求和(MxN, NxP)
        /// </summary>
        /// <param name="target">NxP</param>
        /// <returns>MxP</returns>
        public FloatTensor MatrixMul(FloatTensor target)
        {
            if (this.shape[this.shape.Length - 1] != target.GetDimensionShape(0)) throw new ArgumentException("the shape is not match");
            if(this.shape.Length !=2) throw new ArgumentException("can't calculate the shape of tensor");

            int this_row = this.shape[0];
            int this_col = this.shape[1];
            int target_col = target.GetDimensionShape(1);

            int[] result_shape = new int[] {this_row, target_col };

            float[] result_data = new float[this_row*target_col];

            float[] target_data = target.GetData();

            for (int i = 0; i < result_shape[0]; i++)
            {
                for(int j = 0; j < result_shape[1]; j++)
                {
                   float temp = 0f;
                   for(int k = 0; k < this.shape[1]; k++)
                    {
                        temp += this.data[i * this_col + k] * target_data[k * target_col + j];
                    }
                    result_data[i * target_col+j] = temp;
                }
            }

            FloatTensor result = new FloatTensor(result_data, result_shape);

            return result;
        }

        public void Clamp(float min, float max)
        {
            for(int i = 0; i < this.data.Length; i++)
            {
                if (data[i] < min)
                    data[i] = min;
                else if (data[i] > max)
                    data[i] = max;
            }
        }

        /// <summary>
        /// 为某一行或某一列设置数据
        /// </summary>
        /// <param name="dimension">0为行，1为列</param>
        /// <param name="index">行号或列号</param>
        /// <param name="data">新的行数据或列数据</param>
        public void SetDimensionData(int dimension, int index, FloatTensor data)
        {
            if (this.shape.Length != 2) return;

            int target = 0;
            if (dimension == 0) target = this.shape[1];
            if (dimension == 1) target = this.shape[0];
            if (data.GetLength() != target)
            {
                throw new ArgumentException("新设置的数据长度"+data.GetLength().ToString()+"与矩阵期望长度"+target.ToString()+"不一致");
            }

            int start = 0;
            int stride = 0;
            if (dimension == 0)
            {
                stride = 1;
                start = index * this.shape[1];
               
            }
            if (dimension == 1)
            {
                start = index;
                stride = this.shape[1];
            }

            float[] data_data = data.GetData();
            for(int i=0;i<data_data.Length&&start<this.data.Length;i++, start += stride)
            {
                this.data[start] = data_data[i];
            }

        }

        /// <summary>
        /// 取出一行或一列数据
        /// </summary>
        /// <param name="dimension">0为取一行数据，1为取一列数据</param>
        /// <param name="index">行号或列号</param>
        /// <returns></returns>
        public FloatTensor GetDimensionData(int dimension, int index)
        {
            if (this.shape.Length != 2) return new FloatTensor();
            int result_length = 1;
            int stride = 0;
            int start = 0;
            int[] result_shape = new int[] { 0, 0 };
            if (dimension == 0)
            {
                result_length = this.shape[1];
                stride = 1;
                start = index * result_length;
                result_shape[0] = 1;
                result_shape[1] = this.shape[1];
            }
            if (dimension == 1)
            {
                result_length = this.shape[0];
                start = index;
                stride = this.shape[1];
                result_shape[0] = this.shape[0];
                result_shape[1] = 1;
            }

            float[] datas = new float[result_length];
            for (int i = 0; i < result_length; i++, start += stride)
            {
                datas[i] = this.data[start];
            }

            return new FloatTensor(datas, result_shape);

        }

        public FloatTensor RowAdd(FloatTensor target)
        {
            if (this.shape[1] != target.GetDimensionShape(1) || target.GetDimensionShape(0) != 1) throw new ArgumentException("the target dimension is not match!");

            float[] data = new float[this.data.Length];

            float[] target_data = target.GetData();
            for(int i = 0; i < this.shape[0];i++)
            {
                for(int j = 0; j < this.shape[1]; j++)
                {
                    data[i * this.shape[1] + j] = this.data[i * this.shape[1] + j]+target_data[j]; 
                }
            }
            return new FloatTensor(data, this.shape);
        }

        public FloatTensor ColAdd(FloatTensor target)
        {
            if (this.shape[0] != target.GetDimensionShape(0) || target.GetDimensionShape(1) != 1) throw new ArgumentException("the target dimension is not match!");
            float[] data = new float[this.data.Length];
            float[] target_data = target.GetData();

            for (int i = 0; i < this.shape[0]; i++)
            {
                for (int j = 0; j < this.shape[1]; j++)
                {
                    data[i * this.shape[1] + j] = this.data[i * this.shape[1] + j]+target_data[i];
                }
            }
            return new FloatTensor(data, this.shape);
        }
       
        /// <summary>
        /// 矩阵转置(MxN -> NxM)
        /// </summary>
        /// <returns>NxM</returns>
        public FloatTensor Transpose()
        {
            float[] result_data = new float[this.data.Length];

            for(int i = 0; i < this.shape[0]; i++)
            {
                for(int j = 0; j < this.shape[1]; j++)
                {
                    result_data[j * this.shape[0] + i] = this.data[i * this.shape[1] + j];
                }
            }

            return new FloatTensor(result_data, new int[] { this.shape[1], this.shape[0] });
        }

     
        /// <summary>
        /// 行平均，MxN -> Mx1
        /// </summary>
        /// <returns>Mx1</returns>
        public FloatTensor RowMean()
        {
            int[] result_shape = new int[] { this.shape[0], 1 };
            float[] result_data = new float[this.shape[0]];

            for(int i = 0; i < this.shape[0];i++) {
                float temp = 0f;
                for(int j = 0; j < this.shape[1]; j++)
                {
                    temp += this.data[i * this.shape[1] + j];
                }
                result_data[i] = temp/this.shape[1];
            }
            return new FloatTensor(result_data, result_shape);
        }

        /// <summary>
        ///  求列平均函数，MxN变成1xN
        /// </summary>
        /// <returns>1xN</returns>
        public FloatTensor ColMean()
        {
            int[] result_shape = new int[] {1, this.shape[1] };
            float[] result_data = new float[this.shape[1]];

            for (int i = 0; i < this.shape[1]; i++)
            {
                float temp = 0f;
                for (int j = 0; j < this.shape[0]; j++)
                {
                    temp += this.data[j * this.shape[1] + i];
                }
                result_data[i] = temp/this.shape[0];
            }
            return new FloatTensor(result_data, result_shape);
        }


        /// <summary>
        /// 减法：MxN - MxN
        /// </summary>
        /// <param name="target">MxN</param>
        /// <returns>MxN</returns>
        public FloatTensor Sub(FloatTensor target)
        {
            return this.Add(target.Mul(-1f));
        }


        /// <summary>
        /// 数字除法 MxN / float
        /// </summary>
        /// <param name="target">float</param>
        /// <returns>MxN</returns>
        public FloatTensor Div(float target)
        {
            return this.Mul(1.0f / target);
        }

        /// <summary>
        ///  对行求比例，如[1, 2, 3, 4]->[0.1, 0.2, 0.3, 0.4]
        /// </summary>
        /// <returns>MxN</returns>
        public FloatTensor RowScale()
        {
            if (this.GetShape().Length!=2) throw new ArgumentException("期望的Shape是MxN，现在的Shape长度为"+ this.GetShape().Length.ToString());

            int[] result_shape = new int[] { this.shape[0], this.shape[1] };
            float[] result = new float[this.shape[0] * this.shape[1]];

            for(int i=0;i<this.shape[0]; i++)
            {
                float sum = 0.0f;
                for(int j=0;j<this.shape[1]; j++)
                {
                    sum += this.data[i * this.shape[1] + j];
                }
                for(int j=0;j<this.shape[1]; j++)
                {
                    result[i * this.shape[1] + j] = this.data[i * this.shape[1] + j]/sum;
                }
            }
            return new FloatTensor(result, result_shape);
        }

        /// <summary>
        /// 列求和 MxN -> 1xN
        /// </summary>
        /// <returns>1xN</returns>
        public FloatTensor ColSum()
        {
            int[] result_shape = new int[] { 1, this.shape[1] };
            float[] result_data = new float[this.shape[1]];

            for (int i = 0; i < this.shape[1]; i++)
            {
                float temp = 0f;
                for (int j = 0; j < this.shape[0]; j++)
                {
                    temp += this.data[j * this.shape[1] + i];
                }
                result_data[i] = temp;
            }
            return new FloatTensor(result_data, result_shape);
        }

        /// <summary>
        /// 行求和，MxN -> Mx1
        /// </summary>
        /// <returns>Mx1</returns>
        public FloatTensor RowSum()
        {
            int[] result_shape = new int[] { this.shape[0], 1 };
            float[] result_data = new float[this.shape[0]];

            for (int i = 0; i < this.shape[0]; i++)
            {
                float temp = 0f;
                for (int j = 0; j < this.shape[1]; j++)
                {
                    temp += this.data[i * this.shape[1] + j];
                }
                result_data[i] = temp;
            }
            return new FloatTensor(result_data, result_shape);
        }

        override
        public String ToString()
        {
            String result = "Shape: ";
            for(int i=0;i<this.shape.Length; i++)
            {
                result = result + this.shape[i].ToString() + "x";
            }
            result = result.Trim('x');
            result += "\n";
            if (this.shape.Length == 2)
            {
                for(int i = 0; i < this.shape[0]; i++)
                {
                    for(int j=0; j<this.shape[1];j++)
                    {
                        result = result + this.data[i * this.shape[1] + j].ToString();
                    }
                    result += "\n";
                }
                result += "\n";
            }
            return result;
        }
    }
}
